package iit.projects.etaxi.view.converters;

import iit.projects.etaxi.util.JSFUtils;

import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.ParseException;

import javax.faces.application.FacesMessage;
import javax.faces.component.StateHolder;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;

public class NumberConverter implements Converter, StateHolder {

    private boolean transientValue = false;

    private boolean groupingUsed = false;
    private boolean integerOnly = false;
    private int maxFractionDigits = 0;
    private boolean maxFractionDigitsSpecified = false;
    private int maxIntegerDigits = 0;
    private boolean maxIntegerDigitsSpecified = false;
    private int minFractionDigits = 0;
    private boolean minFractionDigitsSpecified = false;
    private int minIntegerDigits = 0;
    private boolean minIntegerDigitsSpecified = false;
    private boolean positiveOnly = false;
    private boolean zeroAllowed = true;
    private boolean valueBigDecimal = false;

    public NumberConverter() {
    }

    public void setGroupingUsed(boolean groupingUsed) {
        this.groupingUsed = groupingUsed;
    }

    public boolean isGroupingUsed() {
        return groupingUsed;
    }

    public void setIntegerOnly(boolean integerOnly) {
        this.integerOnly = integerOnly;
    }

    public boolean isIntegerOnly() {
        return integerOnly;
    }

    public void setMaxFractionDigits(int maxFractionDigits) {
        this.maxFractionDigits = maxFractionDigits;
        this.maxFractionDigitsSpecified = true;
    }

    public int getMaxFractionDigits() {
        return maxFractionDigits;
    }

    public void setMaxIntegerDigits(int maxIntegerDigits) {
        this.maxIntegerDigits = maxIntegerDigits;
        this.maxIntegerDigitsSpecified = true;
    }

    public int getMaxIntegerDigits() {
        return maxIntegerDigits;
    }

    public void setMinFractionDigits(int minFractionDigits) {
        this.minFractionDigits = minFractionDigits;
        this.minFractionDigitsSpecified = true;
    }

    public int getMinFractionDigits() {
        return minFractionDigits;
    }

    public void setMinIntegerDigits(int minIntegerDigits) {
        this.minIntegerDigits = minIntegerDigits;
        this.minIntegerDigitsSpecified = true;
    }

    public int getMinIntegerDigits() {
        return minIntegerDigits;
    }

    public void setPositiveOnly(boolean positiveOnly) {
        this.positiveOnly = positiveOnly;
    }

    public boolean isPositiveOnly() {
        return positiveOnly;
    }

    public void setZeroAllowed(boolean zeroAllowed) {
        this.zeroAllowed = zeroAllowed;
    }

    public boolean isZeroAllowed() {
        return zeroAllowed;
    }
    
    public boolean isValueBigDecimal() {
    	return valueBigDecimal;
    }

	public void setValueBigDecimal(boolean valueBigDecimal) {
    	this.valueBigDecimal = valueBigDecimal;
    }

    public Object getAsObject(FacesContext facesContext, 
                              UIComponent uiComponent, String string) {

        if (facesContext == null || uiComponent == null) {
            throw new NullPointerException();
        }

        try {

            if (string == null || string.equals("")) {
                return null;
            }

            if (string.startsWith(" ")) {
                throw new ConverterException(JSFUtils.getMessageFromBundle("common.validation.leadingSpaces", 
                                                                           FacesMessage.SEVERITY_ERROR));
            }

            if (string.endsWith(" ")) {
                throw new ConverterException(JSFUtils.getMessageFromBundle("common.validation.endingSpaces", 
                                                                           FacesMessage.SEVERITY_ERROR));
            }
            
            /*if (!string.matches("^-?\\d+$") && !string.matches("^-?\\d+\\.\\d+$")) {
                int maxIntegerPart = maxIntegerDigitsSpecified ? maxIntegerDigits : 12;
                int maxFractionalPart = maxFractionDigitsSpecified ? maxFractionDigits : 4;
                StringBuilder format = new StringBuilder();
                for (int i = 0; i < maxIntegerPart; i++) {
                    format.append("9");
                }
                if (!integerOnly) {
                    format.append(".");
                    for (int i = 0; i < maxFractionalPart; i++) {
                        format.append("9");
                    }
                }
                throw new ConverterException(JSFUtils.getMessageFromBundleWithArgs("common.conversion.invalidFormat", 
                                                                   FacesMessage.SEVERITY_ERROR, 
                                                                   new String[] { format.toString() }));
            }*/

            Number number = null;

            NumberFormat numberFormat = NumberFormat.getNumberInstance();
            numberFormat.setParseIntegerOnly(integerOnly);
            
            // hack - we need to parse value to a BigDecimal
            if (numberFormat instanceof DecimalFormat) {
            	DecimalFormat df = (DecimalFormat) numberFormat;
            	df.setParseBigDecimal(valueBigDecimal);
            }

            // Perform the requested parsing
            number = numberFormat.parse(string);

            int decimalPointIndex = string.indexOf(".");

            if (integerOnly && (decimalPointIndex > 0)) {
                throw new ConverterException(JSFUtils.getMessageFromBundle("common.conversion.notInteger", 
                                                                           FacesMessage.SEVERITY_ERROR));
            }

            if (!zeroAllowed && (number.toString().equals("0"))) {
                if (positiveOnly) {
                    throw new ConverterException(JSFUtils.getMessageFromBundle("common.conversion.notGreaterThanZero", 
                                                                               FacesMessage.SEVERITY_ERROR));
                } else {
                    throw new ConverterException(JSFUtils.getMessageFromBundle("common.conversion.zeroNotAllowed", 
                                                                               FacesMessage.SEVERITY_ERROR));
                }
            }

            if (positiveOnly && (number.doubleValue() < 0)) {
                if (!zeroAllowed) {
                    throw new ConverterException(JSFUtils.getMessageFromBundle("common.conversion.notGreaterThanZero", 
                                                                               FacesMessage.SEVERITY_ERROR));
                } else {
                    throw new ConverterException(JSFUtils.getMessageFromBundle("common.conversion.notPositive", 
                                                                               FacesMessage.SEVERITY_ERROR));
                }

            }

            if (maxIntegerDigitsSpecified || minIntegerDigitsSpecified) {
                int integerPartLength = 
                    (decimalPointIndex > 0) ? string.substring(0, 
                                                               decimalPointIndex).length() : 
                    string.length();

                if (maxIntegerDigitsSpecified) {
                    if (integerOnly && 
                        (integerPartLength > maxIntegerDigits)) {
                        throw new ConverterException(JSFUtils.getMessageFromBundleWithArgs("common.conversion.integer.exceededMaxIntegerDigits", 
                                                                                           FacesMessage.SEVERITY_ERROR, 
                                                                                           new String[] { String.valueOf(maxIntegerDigits) }));

                    } else if (integerPartLength > maxIntegerDigits) {
                        if (decimalPointIndex > 0) {
                            throw new ConverterException(JSFUtils.getMessageFromBundleWithArgs("common.conversion.double.exceededMaxIntegerDigits", 
                                                                                               FacesMessage.SEVERITY_ERROR, 
                                                                                               new String[] { String.valueOf(maxIntegerDigits) }));
                        } else {
                            throw new ConverterException(JSFUtils.getMessageFromBundleWithArgs("common.conversion.integer.exceededMaxIntegerDigits", 
                                                                                               FacesMessage.SEVERITY_ERROR, 
                                                                                               new String[] { String.valueOf(maxIntegerDigits) }));
                        }

                    }
                }

                if (minIntegerDigitsSpecified) {
                    if (integerOnly && 
                        (integerPartLength < minIntegerDigits)) {
                        throw new ConverterException(JSFUtils.getMessageFromBundleWithArgs("common.conversion.integer.requiredMinIntegerDigits", 
                                                                                           FacesMessage.SEVERITY_ERROR, 
                                                                                           new String[] { String.valueOf(minIntegerDigits) }));

                    } else if (integerPartLength < minIntegerDigits) {
                        if (decimalPointIndex > 0) {
                            throw new ConverterException(JSFUtils.getMessageFromBundleWithArgs("common.conversion.double.requiredMinIntegerDigits", 
                                                                                               FacesMessage.SEVERITY_ERROR, 
                                                                                               new String[] { String.valueOf(minIntegerDigits) }));
                        } else {
                            throw new ConverterException(JSFUtils.getMessageFromBundleWithArgs("common.conversion.integer.requiredMinIntegerDigits", 
                                                                                               FacesMessage.SEVERITY_ERROR, 
                                                                                               new String[] { String.valueOf(minIntegerDigits) }));
                        }

                    }
                }
            }

            if (decimalPointIndex > 0) {
                if (maxFractionDigitsSpecified || minFractionDigitsSpecified) {
                    int fractionPartLength = 
                        (string.substring(decimalPointIndex + 1).length());
                    if (maxFractionDigitsSpecified) {
                        if (fractionPartLength > maxFractionDigits) {
                            throw new ConverterException(JSFUtils.getMessageFromBundleWithArgs("common.conversion.double.exceededMaxFractionDigits", 
                                                                                               FacesMessage.SEVERITY_ERROR, 
                                                                                               new String[] { String.valueOf(maxFractionDigits) }));
                        }
                    }
                    if (minFractionDigitsSpecified) {
                        if (fractionPartLength < minFractionDigits) {
                            throw new ConverterException(JSFUtils.getMessageFromBundleWithArgs("common.conversion.double.requiredMinFractionDigits", 
                                                                                               FacesMessage.SEVERITY_ERROR, 
                                                                                               new String[] { String.valueOf(minFractionDigits) }));
                        }
                    }
                }
            }


            return number;
        } catch (ConverterException e) {
            throw e;
        } catch (ParseException e) {
            throw new ConverterException(JSFUtils.getMessageFromBundleWithArgs("common.conversion.notANumber", 
                                                                               FacesMessage.SEVERITY_ERROR, 
                                                                               new String[] { string }));
        } catch (Exception e) {
            throw new ConverterException(e);
        }
    }

    public String getAsString(FacesContext facesContext, 
                              UIComponent uiComponent, Object object) {

        if (facesContext == null || uiComponent == null) {
            throw new NullPointerException();
        }

        try {

            if (object == null) {
                return "";
            }

            if (object instanceof String) {
                return (String)object;
            }

            NumberFormat formatter = NumberFormat.getNumberInstance();
            configureFormatter(formatter);

            // Perform the requested formatting
            return (formatter.format(object));

        } catch (ConverterException e) {
            throw e;
        } catch (Exception e) {
            throw new ConverterException(e);
        }
    }


    /**
     * <p>Configure the specified <code>NumberFormat</code> based on the
     * formatting properties that have been set.</p>
     *
     * @param formatter The <code>NumberFormat</code> instance to configure
     */
    private void configureFormatter(NumberFormat formatter) {
        formatter.setGroupingUsed(groupingUsed);
        if (maxIntegerDigitsSpecified) {
            formatter.setMaximumIntegerDigits(maxIntegerDigits);
        }
        if (minIntegerDigitsSpecified) {
            formatter.setMinimumIntegerDigits(minIntegerDigits);
        }
        if (maxFractionDigitsSpecified) {
            formatter.setMaximumFractionDigits(maxFractionDigits);
        }
        if (minFractionDigitsSpecified) {
            formatter.setMinimumFractionDigits(minFractionDigits);
        }
    }

    public Object saveState(FacesContext facesContext) {
        Object values[] = new Object[13];
        values[0] = isGroupingUsed() ? Boolean.TRUE : Boolean.FALSE;
        values[1] = isIntegerOnly() ? Boolean.TRUE : Boolean.FALSE;
        values[2] = new Integer(maxFractionDigits);
        values[3] = maxFractionDigitsSpecified ? Boolean.TRUE : Boolean.FALSE;
        values[4] = new Integer(maxIntegerDigits);
        values[5] = maxIntegerDigitsSpecified ? Boolean.TRUE : Boolean.FALSE;
        values[6] = new Integer(minFractionDigits);
        values[7] = minFractionDigitsSpecified ? Boolean.TRUE : Boolean.FALSE;
        values[8] = new Integer(minIntegerDigits);
        values[9] = minIntegerDigitsSpecified ? Boolean.TRUE : Boolean.FALSE;
        values[10] = isPositiveOnly() ? Boolean.TRUE : Boolean.FALSE;
        values[11] = isZeroAllowed() ? Boolean.TRUE : Boolean.FALSE;
        values[12] = isValueBigDecimal() ? Boolean.TRUE : Boolean.FALSE;
        return (values);
    }

    public void restoreState(FacesContext facesContext, Object object) {
        Object values[] = (Object[])object;
        groupingUsed = ((Boolean)values[0]).booleanValue();
        integerOnly = ((Boolean)values[1]).booleanValue();
        maxFractionDigits = ((Integer)values[2]).intValue();
        maxFractionDigitsSpecified = ((Boolean)values[3]).booleanValue();
        maxIntegerDigits = ((Integer)values[4]).intValue();
        maxIntegerDigitsSpecified = ((Boolean)values[5]).booleanValue();
        minFractionDigits = ((Integer)values[6]).intValue();
        minFractionDigitsSpecified = ((Boolean)values[7]).booleanValue();
        minIntegerDigits = ((Integer)values[8]).intValue();
        minIntegerDigitsSpecified = ((Boolean)values[9]).booleanValue();
        positiveOnly = ((Boolean)values[10]).booleanValue();
        zeroAllowed = ((Boolean)values[11]).booleanValue();
        valueBigDecimal = ((Boolean)values[12]).booleanValue();
    }

    public boolean isTransient() {
        return this.transientValue;
    }

    public void setTransient(boolean transientValue) {
        this.transientValue = transientValue;
    }
}
